package com.shxzz.easy12306.framework.starter.idempotent.core.handler.spel;

import com.shxzz.easy12306.framework.starter.idempotent.annotation.Idempotent;
import com.shxzz.easy12306.framework.starter.idempotent.core.IdempotentAspect;
import com.shxzz.easy12306.framework.starter.idempotent.core.IdempotentContext;
import com.shxzz.easy12306.framework.starter.idempotent.core.handler.AbstractIdempotentExecuteHandler;
import com.shxzz.easy12306.framework.starter.idempotent.core.handler.IdempotentParamWrapper;
import com.shxzz.easy12306.framework.starter.idempotent.toolkit.SpELUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

/**
 * 适用于 RestAPI 场景， 幂等 key 类型为 SpEL 表达式的幂等处理器
 * <p>
 * 幂等逻辑：当相同的幂等 key 正在执行时，会直接拒绝请求
 * 适用于：接口频繁恶意调用等幂等场景
 * </p>
 *
 * @author shxzz
 * @create 2024-02-22-22:33
 */
@RequiredArgsConstructor
public class IdempotentSpELByRestAPIExecuteHandler extends AbstractIdempotentExecuteHandler implements IdempotentSpELService {

    private final RedissonClient redissonClient;
    private static final String LOCK = "lock:SpEL:RestAPI";

    @SneakyThrows
    @Override
    protected IdempotentParamWrapper buildIdempotentParam(ProceedingJoinPoint proceedingJoinPoint) {
        Idempotent idempotent = IdempotentAspect.getIdempotent(proceedingJoinPoint);
        String key = (String) SpELUtil.parseKey(idempotent.spel(), ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod(), proceedingJoinPoint.getArgs());
        return IdempotentParamWrapper.builder()
                .proceedingJoinPoint(proceedingJoinPoint)
                .idempotent(idempotent)
                .lockKey(key)
                .build();
    }

    @Override
    protected void handler(IdempotentParamWrapper idempotentParamWrapper) {
        RLock lock = redissonClient.getLock(idempotentParamWrapper.getIdempotent().uniqueKeyPrefix() + idempotentParamWrapper.getLockKey());
        if (!lock.tryLock()) {
            throw new RuntimeException(idempotentParamWrapper.getIdempotent().message());
        }
        IdempotentContext.put(LOCK, lock);
    }

    @Override
    public void postProcessing() {
        releaseLock();
    }

    @Override
    public void exceptionProcessing() {
        releaseLock();
    }

    private void releaseLock() {
        RLock lock = null;
        try {
            lock = (RLock) IdempotentContext.getValue(LOCK);
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
    }
}
